package com.ruoyi.framework.aspectj;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.angel.mongodb.annotation.MongoOperateLog;
import com.angel.mongodb.annotation.OperateType;
import com.google.common.collect.Maps;
import com.ruoyi.common.mongo.utils.FormatUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.convert.UpdateMapper;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

/**
 * @description: mongodb日志切面，拦截@MongoDBLogAspect
 * @author: gankench@gmail.com
 * @create: 2021-04-07 19:40
 */
@Aspect
@Component
public class MongoDBLogAspect {
    private final Logger log = LoggerFactory.getLogger(MongoDBLogAspect.class);
    @Autowired
    MongoConverter mongoConverter;
    QueryMapper queryMapper;
    UpdateMapper updateMapper;

    @PostConstruct
    public void init() {
        queryMapper = new QueryMapper(mongoConverter);
        updateMapper = new UpdateMapper(mongoConverter);
    }

    /**
     * 切入点
     */
    @Pointcut("@annotation(com.angel.mongodb.annotation.MongoOperateLog)")
    public void pointcut() {
    }

    /**
     * 环绕通知
     * 环绕通知=前置+目标方法执行+后置通知
     */
    @Around("pointcut()")
    public Object doAround(final ProceedingJoinPoint jp) throws Throwable {
        long time = System.currentTimeMillis();
        Object proceed = jp.proceed();
        time = System.currentTimeMillis() - time;
        Object[] args = jp.getArgs();
        MethodSignature signature = (MethodSignature) jp.getSignature();
        Method method = signature.getMethod();
        String[] parameterNames = signature.getParameterNames();
        Map<String, Object> paramNameValueMap = Maps.newHashMap();
        for (int i = 0; i < parameterNames.length; i++) {
            paramNameValueMap.put(parameterNames[i], args[i]);
        }
        MongoOperateLog operateLog = method.getAnnotation(MongoOperateLog.class);
        StringBuilder sb = new StringBuilder();
        switch (operateLog.operateType()) {
            case SAVE:
                log.info("SAVE exec {} ms ,sql={}", time, logSave(args[0]));
                break;
            case QUERY:
                log.info("QUERY exec {} ms ,sql={}", time, convert(paramNameValueMap, OperateType.QUERY));
                break;
            case COUNT:
                log.info("COUNT exec {} ms ,sql={}", time, convert(paramNameValueMap, OperateType.COUNT));
                break;
            case DELETE:
                log.info("DELETE exec {} ms ,sql={}", time, convert(paramNameValueMap, OperateType.DELETE));
                break;
            case UPDATE:
                log.info("UPDATE exec {} ms ,sql={}", time, convert(paramNameValueMap, OperateType.UPDATE));
                break;
            default:
                log.info("UNKOWN SQL!");
                break;

        }

        return proceed;
    }

    private String convert(Map<String, Object> paramNameValueMap, OperateType type) {
        Query query = new Query();
        Class<?> clazz = new Object().getClass();
        if (paramNameValueMap.containsKey("clazz")) {
            clazz = (Class<?>) paramNameValueMap.get("clazz");
//            if (paramNameValueMap.containsKey("criteriaWrapper")) {
//                query = new Query(((CriteriaWrapper) paramNameValueMap.get("criteriaWrapper")).build());
//            }
//            if (paramNameValueMap.containsKey("ids")) {
//                query = new Query(Criteria.where("_id").in((List<?>) paramNameValueMap.get("ids")));
//            }
            if (paramNameValueMap.containsKey("id")) {
                query = new Query(Criteria.where("_id").is((String) paramNameValueMap.get("id")));
            }
            if (paramNameValueMap.containsKey("query")) {
                query = (Query) paramNameValueMap.get("query");
            }
//            if (paramNameValueMap.containsKey("sortBuilder")) {
//                if (paramNameValueMap.containsKey("")) {
//
//                } else if (paramNameValueMap.containsKey("criteria")) {
//                    query = new Query((Criteria) paramNameValueMap.get("criteria")).with(((SortBuilder) paramNameValueMap.get("sortBuilder")).toSort());
//                } else if (paramNameValueMap.containsKey("criteriaWrapper")) {
//                    query = new Query(((CriteriaWrapper) paramNameValueMap.get("criteriaWrapper")).build()).with(((SortBuilder) paramNameValueMap.get("sortBuilder")).toSort());
//                } else if (paramNameValueMap.containsKey("ids")) {
//                    query = new Query(Criteria.where("_id").in((Collection<String>) paramNameValueMap.get("ids"))).with(((SortBuilder) paramNameValueMap.get("sortBuilder")).toSort());
//                } else {
//                    query = new Query().with(((SortBuilder) paramNameValueMap.get("sortBuilder")).toSort());
//                }
//            }
//            if (paramNameValueMap.containsKey("sortBuilder")) {
//
//            }
        }
        if (OperateType.DELETE.equals(type)) {
            return logDelete(clazz, query).toString();
        } else if (OperateType.QUERY.equals(type)) {
            return logQuery(clazz, query).toString();
        } else if (OperateType.COUNT.equals(type)) {
            return logCount(clazz, query).toString();
        } else if (OperateType.UPDATE.equals(type)) {
//            return logUpdate(clazz, query).toString();
            return "";
        }
        System.out.println(paramNameValueMap);
        return "";
    }

    /**
     * 打印查询语句
     *
     * @param query
     */
    private StringBuffer logQuery(Class<?> clazz, Query query) {
        MongoPersistentEntity<?> entity = mongoConverter.getMappingContext().getPersistentEntity(clazz);
        Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), entity);
        Document mappedField = queryMapper.getMappedObject(query.getFieldsObject(), entity);
        Document mappedSort = queryMapper.getMappedObject(query.getSortObject(), entity);

        StringBuffer log = new StringBuffer("\ndb.")
                .append(StrUtil.lowerFirst(clazz.getSimpleName()))
                .append(".find(")
                .append(FormatUtils.bson(mappedQuery.toJson()));

        if (!query.getFieldsObject().isEmpty()) {
            log.append(",")
                    .append(FormatUtils.bson(mappedField.toJson()))
                    .append(")");
        } else {
            log.append(")");
        }

        if (query.isSorted()) {
            log.append(".sort(")
                    .append(FormatUtils.bson(mappedSort.toJson()))
                    .append(")");
        }

        if (query.getLimit() != 0L) {
            log.append(".limit(")
                    .append(query.getLimit())
                    .append(")");
        }

        if (query.getSkip() != 0L) {
            log.append(".skip(")
                    .append(query.getSkip())
                    .append(")");
        }
        log.append(";");

        return log;
    }

    /**
     * 打印查询数量语句
     *
     * @param query
     */
    private StringBuffer logCount(Class<?> clazz, Query query) {
        MongoPersistentEntity<?> entity = mongoConverter.getMappingContext().getPersistentEntity(clazz);
        Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), entity);
        StringBuffer log = new StringBuffer("\ndb.")
                .append(StrUtil.lowerFirst(clazz.getSimpleName()))
                .append(".find(")
                .append(FormatUtils.bson(mappedQuery.toJson()))
                .append(").count();");
        return log;
    }

    /**
     * 打印查询语句
     *
     * @param query
     */
    private StringBuffer logDelete(Class<?> clazz, Query query) {
        MongoPersistentEntity<?> entity = mongoConverter.getMappingContext().getPersistentEntity(clazz);
        Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), entity);
        StringBuffer log = new StringBuffer("\ndb.")
                .append(StrUtil.lowerFirst(clazz.getSimpleName()))
                .append(".remove(")
                .append(FormatUtils.bson(mappedQuery.toJson()))
                .append(")");
        return log;
    }

    /**
     * 打印查询语句
     *
     * @param query
     */
    private StringBuffer logUpdate(Class<?> clazz, Query query, Update update, boolean multi) {
        MongoPersistentEntity<?> entity = mongoConverter.getMappingContext().getPersistentEntity(clazz);
        Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), entity);
        Document mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity);
        StringBuffer log = new StringBuffer("\ndb.")
                .append(StrUtil.lowerFirst(clazz.getSimpleName()))
                .append(".update(")
                .append(FormatUtils.bson(mappedQuery.toJson()) + ",")
                .append(FormatUtils.bson(mappedUpdate.toJson()) + ",")
                .append(FormatUtils.bson("{multi:" + multi + "});"));
        return log;

    }

    /**
     * 打印查询语句
     *
     * @param object
     */
    private StringBuffer logSave(Object object) {
        if (List.class.isAssignableFrom(object.getClass())) {
            List<?> list = (List<?>) object;
            if (list.size() > 0) {
                Object obj = list.get(0);
                StringBuffer log = new StringBuffer("\ndb.")
                        .append(StrUtil.lowerFirst(obj.getClass().getSimpleName()))
                        .append(".save(")
                        .append(JSONUtil.toJsonPrettyStr(list))
                        .append(");");
                return log;
            }
        } else {
            StringBuffer log = new StringBuffer("\ndb.")
                    .append(object.getClass().getSimpleName())
                    .append(".save(")
                    .append(JSONUtil.toJsonPrettyStr(object))
                    .append(");");
            return log;
        }

        return new StringBuffer();
    }

}

